#include "U2DataStream.h"

#include "U2StringUtil.h"



U2EG_NAMESPACE_USING

template <typename T> U2DataStream& U2DataStream::operator >>(T& val)
{
    read(static_cast<void*>(&val), sizeof(T));
    return *this;
}
//-----------------------------------------------------------------------
U2String U2DataStream::getLine(bool trimAfter)
{
    char tmpBuf[U2_STREAM_TEMP_SIZE];
    U2String retString;
    size_t readCount;
    // Keep looping while not hitting delimiter
    while ((readCount = read(tmpBuf, U2_STREAM_TEMP_SIZE-1)) != 0)
    {
        // Terminate string
        tmpBuf[readCount] = '\0';

        char* p = strchr(tmpBuf, '\n');
        if (p != 0)
        {
            // Reposition backwards
            skip((long)(p + 1 - tmpBuf - readCount));
            *p = '\0';
        }

        retString += tmpBuf;

        if (p != 0)
        {
            // Trim off trailing CR if this was a CR/LF entry
            if (retString.length() && retString[retString.length()-1] == '\r')
            {
                retString.erase(retString.length()-1, 1);
            }

            // Found terminator, break out
            break;
        }
    }

    if (trimAfter)
    {
        static const U2String delims = " \t\r";
        retString.erase(retString.find_last_not_of(delims) + 1); // trim right
        retString.erase(0, retString.find_first_not_of(delims)); // trim left
    }

    return retString;
}
//-----------------------------------------------------------------------
size_t U2DataStream::readLine(char* buf, size_t maxCount, const U2String& delim)
{
    // Deal with both Unix & Windows LFs
    bool trimCR = false;
    if (delim.find_first_of('\n') != U2String::npos)
    {
        trimCR = true;
    }

    char tmpBuf[U2_STREAM_TEMP_SIZE];
    size_t chunkSize = std::min(maxCount, (size_t)U2_STREAM_TEMP_SIZE-1);
    size_t totalCount = 0;
    size_t readCount; 
    while (chunkSize && (readCount = read(tmpBuf, chunkSize)))
    {
        // Terminate
        tmpBuf[readCount] = '\0';

        // Find first delimiter
        size_t pos = strcspn(tmpBuf, delim.c_str());

        if (pos < readCount)
        {
            // Found terminator, reposition backwards
            skip((long)(pos + 1 - readCount));
        }

        // Are we genuinely copying?
        if (buf)
        {
            memcpy(buf+totalCount, tmpBuf, pos);
        }
        totalCount += pos;

        if (pos < readCount)
        {
            // Trim off trailing CR if this was a CR/LF entry
            if (trimCR && totalCount && buf[totalCount-1] == '\r')
            {
                --totalCount;
            }

            // Found terminator, break out
            break;
        }

        // Adjust chunkSize for next time
        chunkSize = std::min(maxCount-totalCount, (size_t)U2_STREAM_TEMP_SIZE-1);
    }

    // Terminate
    buf[totalCount] = '\0';

    return totalCount;
}
//-----------------------------------------------------------------------
size_t U2DataStream::skipLine(const U2String& delim)
{
    char tmpBuf[U2_STREAM_TEMP_SIZE];
    size_t total = 0;
    size_t readCount;
    // Keep looping while not hitting delimiter
    while ((readCount = read(tmpBuf, U2_STREAM_TEMP_SIZE-1)) != 0)
    {
        // Terminate string
        tmpBuf[readCount] = '\0';

        // Find first delimiter
        size_t pos = strcspn(tmpBuf, delim.c_str());

        if (pos < readCount)
        {
            // Found terminator, reposition backwards
            skip((long)(pos + 1 - readCount));

            total += pos + 1;

            // break out
            break;
        }

        total += readCount;
    }

    return total;
}
//-----------------------------------------------------------------------
U2String U2DataStream::getAsString(void)
{
    // Read the entire buffer - ideally in one read, but if the size of
    // the buffer is unknown, do multiple fixed size reads.
    size_t bufSize = (mSize > 0 ? mSize : 4096);
    char* pBuf = U2_ALLOC_T(char, bufSize, MEMCATEGORY_GENERAL);
    // Ensure read from begin of stream
    seek(0);
    U2String result;
    while (!eof())
    {
        size_t nr = read(pBuf, bufSize);
        result.append(pBuf, nr);
    }
    U2_FREE(pBuf, MEMCATEGORY_GENERAL);
    return result;
}




//---------------------------------------------------------------------
void U2DataStream::determineEndianness(Endian requestedEndian)
{
    switch(requestedEndian)
    {
    case ENDIAN_NATIVE:
        mFlipEndian = false;
        break;
    case ENDIAN_BIG:
#if U2_ENDIAN == U2_ENDIAN_BIG
        mFlipEndian = false;
#else
        mFlipEndian = true;
#endif
        break;
    case ENDIAN_LITTLE:
#if U2_ENDIAN == U2_ENDIAN_BIG
        mFlipEndian = true;
#else
        mFlipEndian = false;
#endif
        break;
    }
}
//---------------------------------------------------------------------
void U2DataStream::writeFloats(const float* const pFloat, size_t count)
{
    if (mFlipEndian)
    {
        float * pFloatToWrite = (float *)malloc(sizeof(float) * count);
        memcpy(pFloatToWrite, pFloat, sizeof(float) * count);

        flipToLittleEndian(pFloatToWrite, sizeof(float), count);
        writeData(pFloatToWrite, sizeof(float), count);

        free(pFloatToWrite);
    }
    else
    {
        writeData(pFloat, sizeof(float), count);
    }
}
//---------------------------------------------------------------------
void U2DataStream::writeDoubles(const double* const pDouble, size_t count)
{
    // Convert to float, then write
    float* tmp = U2_ALLOC_T(float, count, MEMCATEGORY_GENERAL);
    for (unsigned int i = 0; i < count; ++i)
    {
        tmp[i] = static_cast<float>(pDouble[i]);
    }
    if(mFlipEndian)
    {
        flipToLittleEndian(tmp, sizeof(float), count);
        writeData(tmp, sizeof(float), count);
    }
    else
    {
        writeData(tmp, sizeof(float), count);
    }
    U2_FREE(tmp, MEMCATEGORY_GENERAL);
}
//---------------------------------------------------------------------
void U2DataStream::writeUnsignedShorts(const unsigned short* const pShort, size_t count = 1)
{
    if(mFlipEndian)
    {
        unsigned short * pShortToWrite = (unsigned short *)malloc(sizeof(unsigned short) * count);
        memcpy(pShortToWrite, pShort, sizeof(unsigned short) * count);

        flipToLittleEndian(pShortToWrite, sizeof(unsigned short), count);
        writeData(pShortToWrite, sizeof(unsigned short), count);

        free(pShortToWrite);
    }
    else
    {
        writeData(pShort, sizeof(unsigned short), count);
    }
}
//---------------------------------------------------------------------
void U2DataStream::writeShorts(const short* const pShort, size_t count = 1)
{
    writeUnsignedShorts((unsigned short*)pShort, count);
}
//---------------------------------------------------------------------
void U2DataStream::writeUnsignedInts(const unsigned int* const pInt, size_t count = 1)
{
    if(mFlipEndian)
    {
        unsigned int * pIntToWrite = (unsigned int *)malloc(sizeof(unsigned int) * count);
        memcpy(pIntToWrite, pInt, sizeof(unsigned int) * count);

        flipToLittleEndian(pIntToWrite, sizeof(unsigned int), count);
        writeData(pIntToWrite, sizeof(unsigned int), count);

        free(pIntToWrite);
    }
    else
    {
        writeData(pInt, sizeof(unsigned int), count);
    }
}
//---------------------------------------------------------------------
void U2DataStream::writeInts(const int* const pInt, size_t count = 1)
{
    writeUnsignedInts((unsigned int*)pInt, count);
}
//---------------------------------------------------------------------
//---------------------------------------------------------------------
void U2DataStream::writeBools(const bool* const pBool, size_t count = 1)
{
    //no endian flipping for 1-byte bools
    //XXX Nasty Hack to convert to 1-byte bools
#if U2_PLATFORM == U2_PLATFORM_APPLE || U2_PLATFORM == U2_PLATFORM_APPLE_IOS
    char * pCharToWrite = (char *)malloc(sizeof(char) * count);
    for(unsigned int i = 0; i < count; i++)
    {
        *(char *)(pCharToWrite + i) = *(bool *)(pBool + i);
    }

    writeData(pCharToWrite, sizeof(char), count);

    free(pCharToWrite);
#else
    writeData(pBool, sizeof(bool), count);
#endif
}
//---------------------------------------------------------------------
void U2DataStream::writeFloat(float val)
{
    writeFloats(&val, 1);
}
//---------------------------------------------------------------------
void U2DataStream::writeDouble(double val)
{
    writeDoubles(&val, 1);
}
//---------------------------------------------------------------------
void U2DataStream::writeUnsignedShort(unsigned short val)
{
    writeUnsignedShorts(&val, 1);
}
//---------------------------------------------------------------------
void U2DataStream::writeShort(short val)
{
    writeShorts(&val, 1);
}
//---------------------------------------------------------------------
void U2DataStream::writeUnsignedInt(unsigned int val)
{
    writeUnsignedInts(&val, 1);
}
//---------------------------------------------------------------------
void U2DataStream::writeInt(int val)
{
    writeInts(&val, 1);
}
//---------------------------------------------------------------------
void U2DataStream::writeBool(bool val)
{
    writeBools(&val, 1);
}
//---------------------------------------------------------------------
void U2DataStream::writeUnsignedChar(unsigned char val)
{
    write(&val, 1);
}
//---------------------------------------------------------------------
void U2DataStream::writeChar(char val)
{
    write(&val, 1);
}

//---------------------------------------------------------------------
void U2DataStream::writeData(const void* const buf, size_t size, size_t count)
{
    this->write(buf, size * count);
}
//---------------------------------------------------------------------
void U2DataStream::writeString(const U2String& string)
{
    // Old, backwards compatible way - \n terminated
    this->write(string.c_str(), string.length());
    // Write terminating newline char
    char terminator = '\n';
    this->write(&terminator, 1);
}
//---------------------------------------------------------------------
void U2DataStream::writeUTFString(const std::string& string)
{
    short len = string.length();
    this->writeShort(len);
    this->write(string.c_str(), string.length());
}
//---------------------------------------------------------------------
void U2DataStream::readBools(bool* pDest, size_t count)
{
    //XXX Nasty Hack to convert 1 byte bools to 4 byte bools
#if U2_PLATFORM == U2_PLATFORM_APPLE || U2_PLATFORM == U2_PLATFORM_APPLE_IOS
    char * pTemp = (char *)malloc(1*count); // to hold 1-byte bools
    this->read(pTemp, 1 * count);
    for(unsigned int i = 0; i < count; i++)
        *(bool *)(pDest + i) = *(char *)(pTemp + i);

    free (pTemp);
#else
    this->read(pDest, sizeof(bool) * count);
#endif
    //no flipping on 1-byte datatypes
}
//---------------------------------------------------------------------
void U2DataStream::readFloats(float* pDest, size_t count)
{
    this->read(pDest, sizeof(float) * count);
    flipFromLittleEndian(pDest, sizeof(float), count);
}
//---------------------------------------------------------------------
void U2DataStream::readDoubles(double* pDest, size_t count)
{
    // Read from float, convert to double
    float* tmp = U2_ALLOC_T(float, count, MEMCATEGORY_GENERAL);
    float* ptmp = tmp;
    this->read(tmp, sizeof(float) * count);
    flipFromLittleEndian(tmp, sizeof(float), count);
    // Convert to doubles (no cast required)
    while(count--)
    {
        *pDest++ = *ptmp++;
    }
    U2_FREE(tmp, MEMCATEGORY_GENERAL);
}
//---------------------------------------------------------------------
void U2DataStream::readUnsignedShorts(unsigned short* pDest, size_t count)
{
    this->read(pDest, sizeof(unsigned short) * count);
    flipFromLittleEndian(pDest, sizeof(unsigned short), count);
}
//---------------------------------------------------------------------
void U2DataStream::readShorts(short* pDest, size_t count)
{
    this->read(pDest, sizeof(short) * count);
    flipFromLittleEndian(pDest, sizeof(short), count);
}
//---------------------------------------------------------------------
void U2DataStream::readUnsignedInts(unsigned int* pDest, size_t count)
{
    this->read(pDest, sizeof(unsigned int) * count);
    flipFromLittleEndian(pDest, sizeof(unsigned int), count);
}
//---------------------------------------------------------------------
void U2DataStream::readInts(int* pDest, size_t count)
{
    this->read(pDest, sizeof(int) * count);
    flipFromLittleEndian(pDest, sizeof(int), count);
}
//---------------------------------------------------------------------
bool U2DataStream::readBool(void)
{
    bool val = false;
    readBools(&val, 1);
    return val;
}
//---------------------------------------------------------------------
float U2DataStream::readFloat(void)
{
    float val = 0.0f;
    readFloats(&val, 1);
    return val;
}
//---------------------------------------------------------------------
double U2DataStream::readDouble(void)
{
    double val = 0.0;
    readDoubles(&val, 1);
    return val;
}
//---------------------------------------------------------------------
unsigned short U2DataStream::readUnsignedShort(void)
{
    unsigned short val = 0;
    readUnsignedShorts(&val, 1);
    return val;
}
//---------------------------------------------------------------------
short U2DataStream::readShort(void)
{
    short val = 0;
    readShorts(&val, 1);
    return val;
}
//---------------------------------------------------------------------
unsigned int U2DataStream::readUnsignedInt(void)
{
    unsigned int val = 0;
    readUnsignedInts(&val, 1);
    return val;
}
//---------------------------------------------------------------------
int U2DataStream::readInt(void)
{
    int val = 0;
    readInts(&val, 1);
    return val;
}
//---------------------------------------------------------------------
unsigned char U2DataStream::readUnsignedChar(void)
{
    unsigned char val = 0;
    read(&val, 1);
    return val;
}
//---------------------------------------------------------------------
char U2DataStream::readChar(void)
{
    char val = 0;
    read(&val, 1);
    return val;
}
//---------------------------------------------------------------------
U2String U2DataStream::readString(size_t numChars)
{
    assert (numChars <= 255);
    char str[255];
    this->read(str, numChars);
    str[numChars] = '\0';
    return str;
}
//---------------------------------------------------------------------
U2String U2DataStream::readString(void)
{
    return this->getLine(false);
}

//---------------------------------------------------------------------
U2String U2DataStream::readUTFString(void)
{
    short numChars = this->readShort();
    assert (numChars <= 255);
    char str[255];
    this->read(str, numChars);
    str[numChars] = '\0';
    return str;
}

void U2DataStream::flipToLittleEndian(void* pData, size_t size, size_t count)
{
    if(mFlipEndian)
    {
        flipEndian(pData, size, count);
    }
}

void U2DataStream::flipFromLittleEndian(void* pData, size_t size, size_t count)
{
    if(mFlipEndian)
    {
        flipEndian(pData, size, count);
    }
}

void U2DataStream::flipEndian(void * pData, size_t size, size_t count)
{
    for(unsigned int index = 0; index < count; index++)
    {
        flipEndian((void *)((size_t)pData + (index * size)), size);
    }
}

void U2DataStream::flipEndian(void * pData, size_t size)
{
    char swapByte;
    for(unsigned int byteIndex = 0; byteIndex < size/2; byteIndex++)
    {
        swapByte = *(char *)((size_t)pData + byteIndex);
        *(char *)((size_t)pData + byteIndex) = *(char *)((size_t)pData + size - byteIndex - 1);
        *(char *)((size_t)pData + size - byteIndex - 1) = swapByte;
    }
}

bool U2DataStream::isFlipEndian(void)
{
    return mFlipEndian;
}

int U2DataStream::getEndian(void)
{
    return mEndian;
}